package cn.iocoder.yudao.module.trade.order.service.calculator;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.api.promotion.activity.enums.PromotionDiscountTypeEnum;
import cn.iocoder.yudao.module.api.promotion.activity.enums.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.api.promotion.activity.enums.PromotionTypeEnum;
import cn.iocoder.yudao.module.api.promotion.coupon.CouponRecordApi;
import cn.iocoder.yudao.module.api.promotion.coupon.CouponTemplateApi;
import cn.iocoder.yudao.module.api.promotion.coupon.dto.CouponRespDTO;
import cn.iocoder.yudao.module.api.promotion.coupon.dto.CouponTemplateRespDTO;
import cn.iocoder.yudao.module.api.promotion.coupon.enums.CouponStatusEnum;
import cn.iocoder.yudao.module.api.trade.order.enums.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.order.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.order.bo.TradePriceCalculateRespBO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.api.trade.ErrorCodeConstants.PRICE_CALCULATE_COUPON_CAN_NOT_USE;
import static cn.iocoder.yudao.module.api.trade.ErrorCodeConstants.PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER;

/**
 * 优惠劵的 {@link TradePriceCalculator} 实现类
 *
 * @author 芋道源码
 */
@Slf4j
@Component
@Order(TradePriceCalculator.ORDER_COUPON)
public class TradeCouponPriceCalculator implements TradePriceCalculator {

    @Resource
    private CouponTemplateApi couponTemplateApi;
    @Resource
    private CouponRecordApi couponRecordApi;

    @Override
    public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
        // 0. 非优惠活动订单，才计算该优惠
        if (Arrays.asList(TradeOrderTypeEnum.SECKILL.getType(), TradeOrderTypeEnum.BARGAIN.getType(),
                TradeOrderTypeEnum.COMBINATION.getType(), TradeOrderTypeEnum.POINT.getType(),
                TradeOrderTypeEnum.GROUPON.getType()).contains(result.getType())) {
            log.debug("[calculate][只有非优惠活动订单({})，才处理用户等级优惠]", result.getType());
            if (param.getCouponId() != null) {
                throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
            }
            return;
        }

        // 1.1 加载用户的优惠劵列表
        List<CouponTemplateRespDTO> couponTemplates = couponTemplateApi.getCanTakeCouponTemplateListByShopId(param.getShopId());
        List<CouponRespDTO> coupons = couponRecordApi.getCouponListByUserId(param.getUserId(), CouponStatusEnum.UNUSED.getStatus());
        // 排除核销券
        couponTemplates.removeIf(coupon -> Objects.equals(coupon.getProductScope(), PromotionProductScopeEnum.VERIFY.getScope()));
        // 排除已过期的
        coupons.removeIf(coupon -> LocalDateTimeUtils.beforeNow(coupon.getValidEndTime()));
        // 1.2 计算优惠劵的使用条件
        result.setCoupons(calculateCoupons(couponTemplates, coupons, result));

        // 2. 校验优惠劵是否可用
        if (param.getCouponId() == null || param.getCouponId() <= 0) {
            log.debug("[calculate][优惠劵为空，不处理优惠劵]");
            return;
        }
        TradePriceCalculateRespBO.Coupon couponBO = CollUtil.findOne(result.getCoupons(), item -> item.getId().equals(param.getCouponId()));
        CouponRespDTO coupon = CollUtil.findOne(coupons, item -> item.getId().equals(param.getCouponId()));
        if (couponBO == null || coupon == null) {
            throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, "优惠劵不存在");
        }
        if (Boolean.FALSE.equals(couponBO.getMatch())) {
            throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, couponBO.getMismatchReason());
        }

        // 3.1 计算可以优惠的金额
        List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
        Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
        Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
        // 3.2 计算分摊的优惠金额
        List<Integer> divideCouponPrices = TradePriceCalculatorHelper.dividePrice(orderItems, couponPrice);

        // 4.1 记录使用的优惠劵
        result.setCouponId(param.getCouponId());
        // 4.2 记录优惠明细
        TradePriceCalculatorHelper.addPromotion(result, orderItems,
                param.getCouponId(), couponBO.getName(), PromotionTypeEnum.COUPON.getValue(),
                StrUtil.format("优惠劵：省 {} 元", TradePriceCalculatorHelper.formatPrice(couponPrice)),
                divideCouponPrices);
        // 4.3 更新 SKU 优惠金额
        for (int i = 0, n = orderItems.size(); i < n; i++) {
            TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i);
            orderItem.setCouponPrice(divideCouponPrices.get(i));
            TradePriceCalculatorHelper.recountPayPrice(orderItem);
        }
        TradePriceCalculatorHelper.recountAllPrice(result);
        log.debug("[calculate][优惠券价格计算完成] {}", result.getPrice());
    }

    /**
     * 计算用户的优惠劵列表（可用 + 不可用）
     *
     * @param couponTemplates 优惠劵模板
     * @param coupons 已领取的优惠劵
     * @param result 计算结果
     * @return 优惠劵列表
     */
    private List<TradePriceCalculateRespBO.Coupon> calculateCoupons(List<CouponTemplateRespDTO> couponTemplates, List<CouponRespDTO> coupons, TradePriceCalculateRespBO result) {
        List<TradePriceCalculateRespBO.Coupon> couponList = new ArrayList<>();
        List<Long> excludeCouponIds = new ArrayList<>();
        for (CouponTemplateRespDTO couponTemplate : couponTemplates) {
            boolean match = false;
            for (CouponRespDTO coupon : coupons) {
                if (Objects.equals(couponTemplate.getId(), coupon.getTemplateId())) {
                    couponList.add(calculateMatchCoupon(result, coupon));
                    excludeCouponIds.add(coupon.getId());
                    match = true;
                    break;
                }
            }
            if (!match) {
                TradePriceCalculateRespBO.Coupon unmatchCoupon = BeanUtils.toBean(couponTemplate, TradePriceCalculateRespBO.Coupon.class);
                unmatchCoupon.setMatch(false);
                unmatchCoupon.setIsReceive(false);
                unmatchCoupon.setMismatchReason("优惠劵还未领取");
                couponList.add(unmatchCoupon);
            }
        }
        for (CouponRespDTO coupon : coupons) {
            if (!excludeCouponIds.contains(coupon.getId())) {
                couponList.add(calculateMatchCoupon(result, coupon));
                break;
            }
        }
        return couponList;
    }

    private TradePriceCalculateRespBO.Coupon calculateMatchCoupon(TradePriceCalculateRespBO result, CouponRespDTO coupon) {
        TradePriceCalculateRespBO.Coupon matchCoupon = BeanUtils.toBean(coupon, TradePriceCalculateRespBO.Coupon.class);
        matchCoupon.setIsReceive(true);
        // 1.1 优惠劵未到使用时间
        if (LocalDateTimeUtils.afterNow(coupon.getValidStartTime())) {
            matchCoupon.setMatch(false);
            matchCoupon.setMismatchReason("未到使用时间");
            return matchCoupon;
        }
        // 1.2 优惠劵没有匹配的产品
        List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
        if (CollUtil.isEmpty(orderItems)) {
            matchCoupon.setMatch(false);
            matchCoupon.setMismatchReason("没有匹配的产品");
            return matchCoupon;
        }
        // 1.3 差 %1$,.2f 元可用优惠劵
        Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
        if (totalPayPrice < coupon.getUsePrice()) {
            matchCoupon.setMatch(false);
            matchCoupon.setMismatchReason(String.format("差 %1$,.2f 元可用", (coupon.getUsePrice() - totalPayPrice) / 100D));
            return matchCoupon;
        }
        // 1.4 优惠金额超过订单金额
        Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
        if (couponPrice >= totalPayPrice) {
            matchCoupon.setMatch(false);
            matchCoupon.setMismatchReason("优惠金额超过订单金额");
            return matchCoupon;
        }
        // 2. 满足条件
        matchCoupon.setMatch(true);
        return matchCoupon;
    }

    private Integer getCouponPrice(CouponRespDTO coupon, Integer totalPayPrice) {
        if (PromotionDiscountTypeEnum.PRICE.getType().equals(coupon.getDiscountType())) { // 减价
            return coupon.getDiscountPrice();
        } else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(coupon.getDiscountType())) { // 打折
            int couponPrice = totalPayPrice - (totalPayPrice * coupon.getDiscountPercent() / 100);
            return coupon.getDiscountLimitPrice() == null ? couponPrice
                    : Math.min(couponPrice, coupon.getDiscountLimitPrice()); // 优惠上限
        }
        throw new IllegalArgumentException(String.format("优惠劵(%s) 的优惠类型不正确", coupon));
    }

    /**
     * 获得优惠劵可使用的订单项（产品）列表
     *
     * @param result 计算结果
     * @param coupon 优惠劵
     * @return 订单项（产品）列表
     */
    private List<TradePriceCalculateRespBO.OrderItem> filterMatchCouponOrderItems(TradePriceCalculateRespBO result, CouponRespDTO coupon) {
        Predicate<TradePriceCalculateRespBO.OrderItem> matchPredicate = TradePriceCalculateRespBO.OrderItem::getSelected;
        if (PromotionProductScopeEnum.SPU.getScope().equals(coupon.getProductScope())) {
            matchPredicate = matchPredicate // 额外加如下条件
                    .and(orderItem -> coupon.getProductScopeValues().contains(orderItem.getSpuId()));
        } else if (PromotionProductScopeEnum.CATEGORY.getScope().equals(coupon.getProductScope())) {
            matchPredicate = matchPredicate // 额外加如下条件
                    .and(orderItem -> coupon.getProductScopeValues().contains(orderItem.getCategoryId()));
        }
        return filterList(result.getItems(), matchPredicate);
    }

}
